// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/app_list_item_list.h"

#include <stddef.h>

#include <utility>

#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/app_list/app_list_folder_item.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list_observer.h"

namespace app_list {

namespace {

    class TestObserver : public AppListItemListObserver {
    public:
        TestObserver()
            : items_added_(0)
            , items_removed_(0)
            , items_moved_(0)
        {
        }

        ~TestObserver() override { }

        // AppListItemListObserver overriden:
        void OnListItemAdded(size_t index, AppListItem* item) override
        {
            ++items_added_;
        }

        void OnListItemRemoved(size_t index, AppListItem* item) override
        {
            ++items_removed_;
        }

        void OnListItemMoved(size_t from_index,
            size_t to_index,
            AppListItem* item) override
        {
            ++items_moved_;
        }

        size_t items_added() const { return items_added_; }
        size_t items_removed() const { return items_removed_; }
        size_t items_moved() const { return items_moved_; }

        void ResetCounts()
        {
            items_added_ = 0;
            items_removed_ = 0;
            items_moved_ = 0;
        }

    private:
        size_t items_added_;
        size_t items_removed_;
        size_t items_moved_;

        DISALLOW_COPY_AND_ASSIGN(TestObserver);
    };

    std::string GetItemId(int id)
    {
        return base::StringPrintf("Item %d", id);
    }

} // namespace

class AppListItemListTest : public testing::Test {
public:
    AppListItemListTest() { }
    ~AppListItemListTest() override { }

    // testing::Test overrides:
    void SetUp() override { item_list_.AddObserver(&observer_); }

    void TearDown() override { item_list_.RemoveObserver(&observer_); }

protected:
    AppListItem* FindItem(const std::string& id)
    {
        return item_list_.FindItem(id);
    }

    bool FindItemIndex(const std::string& id, size_t* index)
    {
        return item_list_.FindItemIndex(id, index);
    }

    scoped_ptr<AppListItem> CreateItem(const std::string& name)
    {
        scoped_ptr<AppListItem> item(new AppListItem(name));
        size_t nitems = item_list_.item_count();
        syncer::StringOrdinal position;
        if (nitems == 0)
            position = syncer::StringOrdinal::CreateInitialOrdinal();
        else
            position = item_list_.item_at(nitems - 1)->position().CreateAfter();
        item->set_position(position);
        return item;
    }

    AppListItem* CreateAndAddItem(const std::string& name)
    {
        scoped_ptr<AppListItem> item(CreateItem(name));
        return item_list_.AddItem(std::move(item));
    }

    scoped_ptr<AppListItem> RemoveItem(const std::string& id)
    {
        return item_list_.RemoveItem(id);
    }

    scoped_ptr<AppListItem> RemoveItemAt(size_t index)
    {
        return item_list_.RemoveItemAt(index);
    }

    syncer::StringOrdinal CreatePositionBefore(
        const syncer::StringOrdinal& position)
    {
        return item_list_.CreatePositionBefore(position);
    }

    bool VerifyItemListOrdinals()
    {
        bool res = true;
        for (size_t i = 1; i < item_list_.item_count(); ++i) {
            res &= (item_list_.item_at(i - 1)->position().LessThan(
                item_list_.item_at(i)->position()));
        }
        if (!res)
            PrintItems();
        return res;
    }

    bool VerifyItemOrder4(size_t a, size_t b, size_t c, size_t d)
    {
        if ((GetItemId(a) == item_list_.item_at(0)->id()) && (GetItemId(b) == item_list_.item_at(1)->id()) && (GetItemId(c) == item_list_.item_at(2)->id()) && (GetItemId(d) == item_list_.item_at(3)->id()))
            return true;
        PrintItems();
        return false;
    }

    void PrintItems()
    {
        VLOG(1) << "ITEMS:";
        for (size_t i = 0; i < item_list_.item_count(); ++i)
            VLOG(1) << " " << item_list_.item_at(i)->ToDebugString();
    }

    AppListItemList item_list_;
    TestObserver observer_;

private:
    DISALLOW_COPY_AND_ASSIGN(AppListItemListTest);
};

TEST_F(AppListItemListTest, FindItemIndex)
{
    AppListItem* item_0 = CreateAndAddItem(GetItemId(0));
    AppListItem* item_1 = CreateAndAddItem(GetItemId(1));
    AppListItem* item_2 = CreateAndAddItem(GetItemId(2));
    EXPECT_EQ(observer_.items_added(), 3u);
    EXPECT_EQ(item_list_.item_count(), 3u);
    EXPECT_EQ(item_0, item_list_.item_at(0));
    EXPECT_EQ(item_1, item_list_.item_at(1));
    EXPECT_EQ(item_2, item_list_.item_at(2));
    EXPECT_TRUE(VerifyItemListOrdinals());

    size_t index;
    EXPECT_TRUE(FindItemIndex(item_0->id(), &index));
    EXPECT_EQ(index, 0u);
    EXPECT_TRUE(FindItemIndex(item_1->id(), &index));
    EXPECT_EQ(index, 1u);
    EXPECT_TRUE(FindItemIndex(item_2->id(), &index));
    EXPECT_EQ(index, 2u);

    scoped_ptr<AppListItem> item_3(CreateItem(GetItemId(3)));
    EXPECT_FALSE(FindItemIndex(item_3->id(), &index));
}

TEST_F(AppListItemListTest, RemoveItemAt)
{
    AppListItem* item_0 = CreateAndAddItem(GetItemId(0));
    AppListItem* item_1 = CreateAndAddItem(GetItemId(1));
    AppListItem* item_2 = CreateAndAddItem(GetItemId(2));
    EXPECT_EQ(item_list_.item_count(), 3u);
    EXPECT_EQ(observer_.items_added(), 3u);
    size_t index;
    EXPECT_TRUE(FindItemIndex(item_1->id(), &index));
    EXPECT_EQ(index, 1u);
    EXPECT_TRUE(VerifyItemListOrdinals());

    scoped_ptr<AppListItem> item_removed = RemoveItemAt(1);
    EXPECT_EQ(item_removed.get(), item_1);
    EXPECT_FALSE(FindItem(item_1->id()));
    EXPECT_EQ(item_list_.item_count(), 2u);
    EXPECT_EQ(observer_.items_removed(), 1u);
    EXPECT_EQ(item_list_.item_at(0), item_0);
    EXPECT_EQ(item_list_.item_at(1), item_2);
    EXPECT_TRUE(VerifyItemListOrdinals());
}

TEST_F(AppListItemListTest, RemoveItem)
{
    AppListItem* item_0 = CreateAndAddItem(GetItemId(0));
    AppListItem* item_1 = CreateAndAddItem(GetItemId(1));
    AppListItem* item_2 = CreateAndAddItem(GetItemId(2));
    EXPECT_EQ(item_list_.item_count(), 3u);
    EXPECT_EQ(observer_.items_added(), 3u);
    EXPECT_EQ(item_0, item_list_.item_at(0));
    EXPECT_EQ(item_1, item_list_.item_at(1));
    EXPECT_EQ(item_2, item_list_.item_at(2));
    EXPECT_TRUE(VerifyItemListOrdinals());

    size_t index;
    EXPECT_TRUE(FindItemIndex(item_1->id(), &index));
    EXPECT_EQ(index, 1u);

    scoped_ptr<AppListItem> item_removed = RemoveItem(item_1->id());
    EXPECT_EQ(item_removed.get(), item_1);
    EXPECT_FALSE(FindItem(item_1->id()));
    EXPECT_EQ(item_list_.item_count(), 2u);
    EXPECT_EQ(observer_.items_removed(), 1u);
    EXPECT_TRUE(VerifyItemListOrdinals());
}

TEST_F(AppListItemListTest, MoveItem)
{
    CreateAndAddItem(GetItemId(0));
    CreateAndAddItem(GetItemId(1));
    CreateAndAddItem(GetItemId(2));
    CreateAndAddItem(GetItemId(3));

    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));

    item_list_.MoveItem(0, 0);
    EXPECT_EQ(0u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));

    item_list_.MoveItem(0, 1);
    EXPECT_EQ(1u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));

    item_list_.MoveItem(1, 2);
    EXPECT_EQ(2u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 2, 0, 3));

    item_list_.MoveItem(2, 1);
    EXPECT_EQ(3u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));

    item_list_.MoveItem(3, 0);
    EXPECT_EQ(4u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(3, 1, 0, 2));

    item_list_.MoveItem(0, 3);
    EXPECT_EQ(5u, observer_.items_moved());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));
}

TEST_F(AppListItemListTest, SamePosition)
{
    CreateAndAddItem(GetItemId(0));
    CreateAndAddItem(GetItemId(1));
    CreateAndAddItem(GetItemId(2));
    CreateAndAddItem(GetItemId(3));
    item_list_.SetItemPosition(item_list_.item_at(2),
        item_list_.item_at(1)->position());
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));
    EXPECT_TRUE(item_list_.item_at(1)->position().Equals(
        item_list_.item_at(2)->position()));
    // Moving an item to position 1 should fix the position.
    observer_.ResetCounts();
    item_list_.MoveItem(0, 1);
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));
    EXPECT_TRUE(item_list_.item_at(0)->position().LessThan(
        item_list_.item_at(1)->position()));
    EXPECT_TRUE(item_list_.item_at(1)->position().LessThan(
        item_list_.item_at(2)->position()));
    EXPECT_TRUE(item_list_.item_at(2)->position().LessThan(
        item_list_.item_at(3)->position()));
    // One extra move notification for fixed position.
    EXPECT_EQ(2u, observer_.items_moved());

    // Restore the original order.
    item_list_.MoveItem(1, 0);
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));

    // Set all four items to the same position.
    item_list_.SetItemPosition(item_list_.item_at(1),
        item_list_.item_at(0)->position());
    item_list_.SetItemPosition(item_list_.item_at(2),
        item_list_.item_at(0)->position());
    item_list_.SetItemPosition(item_list_.item_at(3),
        item_list_.item_at(0)->position());
    // Move should fix the position of the items.
    observer_.ResetCounts();
    item_list_.MoveItem(0, 1);
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));
    EXPECT_TRUE(item_list_.item_at(0)->position().LessThan(
        item_list_.item_at(1)->position()));
    EXPECT_TRUE(item_list_.item_at(1)->position().LessThan(
        item_list_.item_at(2)->position()));
    EXPECT_TRUE(item_list_.item_at(2)->position().LessThan(
        item_list_.item_at(3)->position()));
    // One extra move notification for fixed position.
    EXPECT_EQ(2u, observer_.items_moved());
}

TEST_F(AppListItemListTest, CreatePositionBefore)
{
    CreateAndAddItem(GetItemId(0));
    syncer::StringOrdinal position0 = item_list_.item_at(0)->position();
    syncer::StringOrdinal new_position;
    new_position = CreatePositionBefore(position0.CreateBefore());
    EXPECT_TRUE(new_position.LessThan(position0));
    new_position = CreatePositionBefore(position0);
    EXPECT_TRUE(new_position.LessThan(position0));
    new_position = CreatePositionBefore(position0.CreateAfter());
    EXPECT_TRUE(new_position.GreaterThan(position0));

    CreateAndAddItem(GetItemId(1));
    syncer::StringOrdinal position1 = item_list_.item_at(1)->position();
    EXPECT_TRUE(position1.GreaterThan(position0));
    new_position = CreatePositionBefore(position1);
    EXPECT_TRUE(new_position.GreaterThan(position0));
    EXPECT_TRUE(new_position.LessThan(position1));

    // Invalid ordinal should return a position at the end of the list.
    new_position = CreatePositionBefore(syncer::StringOrdinal());
    EXPECT_TRUE(new_position.GreaterThan(position1));
}

TEST_F(AppListItemListTest, SetItemPosition)
{
    CreateAndAddItem(GetItemId(0));
    CreateAndAddItem(GetItemId(1));
    CreateAndAddItem(GetItemId(2));
    CreateAndAddItem(GetItemId(3));
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));

    // No change to position.
    item_list_.SetItemPosition(item_list_.item_at(0),
        item_list_.item_at(0)->position());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));
    // No order change.
    item_list_.SetItemPosition(item_list_.item_at(0),
        item_list_.item_at(0)->position().CreateBetween(
            item_list_.item_at(1)->position()));
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(0, 1, 2, 3));
    // 0 -> 1
    item_list_.SetItemPosition(item_list_.item_at(0),
        item_list_.item_at(1)->position().CreateBetween(
            item_list_.item_at(2)->position()));
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 0, 2, 3));
    // 1 -> 2
    item_list_.SetItemPosition(item_list_.item_at(1),
        item_list_.item_at(2)->position().CreateBetween(
            item_list_.item_at(3)->position()));
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(1, 2, 0, 3));
    // 0 -> last
    item_list_.SetItemPosition(item_list_.item_at(0),
        item_list_.item_at(3)->position().CreateAfter());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(2, 0, 3, 1));
    // last -> last
    item_list_.SetItemPosition(item_list_.item_at(3),
        item_list_.item_at(3)->position().CreateAfter());
    EXPECT_TRUE(VerifyItemListOrdinals());
    EXPECT_TRUE(VerifyItemOrder4(2, 0, 3, 1));
}

} // namespace app_list
